/**
 * Copyright (c) 2018-2028, HQY 胡庆一 (215218393@qq.com).
 * <p>
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocm.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ocm.product.dto.AttrValueDTO;
import com.ocm.product.dto.SkuDTO;
import com.ocm.product.dto.SpuDTO;
import com.ocm.product.dto.SpuSearchDTO;
import com.ocm.product.service.IAttrValueService;
import com.ocm.product.service.ICategoryService;
import com.ocm.product.service.ISkuService;
import com.ocm.product.service.ISpuService;
import lombok.SneakyThrows;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import com.ocm.core.mp.base.BaseEntity;
import com.ocm.core.mp.support.Query;
import com.ocm.core.secure.BladeUser;
import com.ocm.core.tool.utils.BeanUtil;
import com.ocm.core.tool.utils.StringUtil;
import com.ocm.order.dto.OrderDetailDTO;
import com.ocm.product.entity.AttrValue;
import com.ocm.product.entity.Sku;
import com.ocm.product.entity.Spu;
import com.ocm.product.es.AttrES;
import com.ocm.product.es.SpuES;
import com.ocm.product.service.*;
import com.ocm.product.utils.ElasticSearchUtil;
import com.ocm.product.vo.AttrValueVO;
import com.ocm.product.vo.SpuDetailVO;
import com.ocm.product.vo.SpuSearchVO;
import com.ocm.product.vo.SpuVO;
import com.ocm.product.mapper.SpuMapper;
import com.ocm.core.mp.base.BaseServiceImpl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import static com.ocm.product.utils.ElasticSearchUtil.COMMON_OPTIONS;

/**
 *  服务实现类
 *
 * @author HQY
 * @since 2021-11-18
 */
@Service
public class SpuServiceImpl extends BaseServiceImpl<SpuMapper, Spu> implements ISpuService {

	@Resource
	private ISkuService skuService;

	@Resource
	private IAttrValueService attrValueService;

	@Resource
	private ICategoryService categoryService;

	@Resource
	private ThreadPoolExecutor executor;

	@Resource
	ElasticSearchUtil esUtil;

	@Resource
	private RestHighLevelClient restHighLevelClient;

	@Override
	public OrderDetailDTO getOrderData(Long skuId) {
		return baseMapper.selectOrderDetail(skuId);
	}

	@Override
	public IPage<SpuVO> selectSpuPage(IPage<SpuVO> page, QueryWrapper<SpuVO> queryWrapper) {
		return page.setRecords(baseMapper.selectSpuPage(page, queryWrapper));
	}

	@Override
	public SpuDetailVO detail(Long spuId){
		try {
			//获取spu信息
			CompletableFuture<SpuDetailVO> spuFuture =CompletableFuture.supplyAsync(
				()-> baseMapper.selectSpuDetailVo(spuId)
				,executor);
			//获取属性信息
			CompletableFuture<List<AttrValueVO>> attrsFuture =CompletableFuture.supplyAsync(
				()-> attrValueService.selectAttrValue(new QueryWrapper<AttrValueVO>()
					.eq("v.spu_id",spuId)
					.groupBy("v.attr_id")
					.orderByAsc(" v.value_rank")
				)
				,executor);

			CompletableFuture<Void> result = CompletableFuture.allOf(spuFuture,attrsFuture);
			//后期活动优惠等查询均使用线程池异步执行
			//阻塞等待任务全部完成
			result.get();

			SpuDetailVO spuDetailVO = spuFuture.get();
			List<AttrValueVO> attrValueVOS = attrsFuture.get();
			spuDetailVO.setAttrValues(attrValueVOS);

			return spuDetailVO;
		}catch(Exception e){
			e.printStackTrace();
			return null;
		}

	}

	@Override
	public SpuVO getSpuVo(Long id) {
		return baseMapper.selectSpuVo(id);
	}

	@Override
	public boolean saveProduct(SpuDTO spuDTO, BladeUser bladeUser) {
		//校验身份
		if(bladeUser==null)return false;
		if(bladeUser.getShopId() ==0) return false;

		//创建时间等初始信息
		Date date = new Date();
		BaseEntity baseEntity = new BaseEntity(bladeUser.getUserId(),date,null,null,1,0);

		//插入spu
		BeanUtil.copy(baseEntity,spuDTO);
		spuDTO.setShopId(bladeUser.getShopId());
		baseMapper.insert(spuDTO);

		//插入sku
		List<? extends Sku> skuData = spuDTO.getSkuData();
		List<Sku> collect = skuData.stream().peek(t -> {
			t.setSpuId(spuDTO.getId());
			BeanUtil.copy(baseEntity,t);
		}).collect(Collectors.toList());
		skuService.saveBatch(collect, 100);

		//插入attr
		List<SkuDTO> skus = spuDTO.getSkuData();
		List<Long> attrIds = spuDTO.getSaleAttrId();

		//插入spuAttr
		List<AttrValue> attr = spuDTO.getAttr().stream().peek(
			attrValue -> {
				attrValue.setSpuId(spuDTO.getId());
				attrValue.setSkuId(0L);
			}
		).collect(Collectors.toList());
		//插入skuAttr
		List<Long> skuIds = skuData.stream().map(Sku::getId).collect(Collectors.toList());
		for(int i=0;i<skus.size();i++){
			List<String> saleAttr = skus.get(i).getSaleAttr();
			for(int t = 0;t<saleAttr.size();t++){
				attr.add(new AttrValue(attrIds.get(t),saleAttr.get(t),spuDTO.getId(),skuIds.get(i),t==0?0:1));
			}
		}
		attrValueService.saveBatch(attr, 100);

		return true;
	}

	//商品上架服务
	@Override
	public boolean putaway(SpuVO spu) {
		//todo 逻辑待优化
		try {
			//复制spu属性
			SpuES productES = new SpuES();
			BeanUtil.copy(spu,productES);
			productES.setSpuId(spu.getId());
			productES.setIsV(spu.getShopType()==0);
			productES.setHasVideo(StringUtil.isNotBlank(spu.getSpuVideo()));
			//设置初始属性 todo 此处热度功能待写
			productES.setHotStore(0L);
			productES.setHasStock(true);
			//设置分类名 todo 此处应改为从缓存中读取
			productES.setCategoryName(categoryService.getCategoryName(spu.getSpuCategory()));

			//获取最低价格及销量
			HashMap<String, Object> Aggregation = skuService.selectAggregation(spu.getId());
			productES.setSaleCount(Long.parseLong(Aggregation.get("sale_count").toString()));
			productES.setPriceMin((BigDecimal) Aggregation.get("price_min"));

			//设置属性对应的es模型
			List<AttrValueDTO> attrValueDTOS = attrValueService.selectAttrSaleBySpuId(spu.getId());
			ArrayList<AttrES> attrs = new ArrayList<>();

			//复制及设置属性模型
			//多数据通过拼接获取 以;进行分割 节约传输
			attrValueDTOS.forEach(t->{
				AttrES copy = BeanUtil.copy(t, AttrES.class);
				copy.setAttrValue(Arrays.asList(t.getAttrValue().split(";")));
				attrs.add(copy);
			});
			productES.setAttrs(attrs);
			//保存数据
			esUtil.addData(productES,"product",spu.getId().toString());

		} catch (Exception e) {
			return false;
		}
		return true;
	}

	@Override
	public IPage<SpuSearchVO> searchSpu(SpuSearchDTO search, Query query) throws IOException {
		SearchRequest r=buildSearchRequest(search,query);
		SearchResponse response = restHighLevelClient.search(r, COMMON_OPTIONS);
		return buildSearchResult(response, query);
	}

	/*构建查询返回结果*/
	private IPage<SpuSearchVO> buildSearchResult(SearchResponse response, Query query) {
		SearchHits hits = response.getHits();

		SpuSearchVO spuSearchVO = new SpuSearchVO();
		List<SpuES> spuESList = new ArrayList<>();
		List<AttrES> attrEsList=new ArrayList<>();

		//获取查询结果
		if(hits.getHits()!=null&&hits.getHits().length>0) {
			for (SearchHit hit : hits.getHits()) {
				String sourceAsString = hit.getSourceAsString();
				spuESList.add(JSON.parseObject(sourceAsString, SpuES.class));
			}
		}

		//获取聚合结果
		ParsedNested attr_agg = response.getAggregations().get("attr_agg");
		ParsedLongTerms attrs_id_agg = attr_agg.getAggregations().get("attrs_id_agg");
		for (Terms.Bucket bucket : attrs_id_agg.getBuckets()) {
			AttrES attrEs = new AttrES();
			attrEs.setId(bucket.getKeyAsNumber().longValue());
			attrEs.setAttrName(((ParsedStringTerms)bucket.getAggregations().get("attrs_name_agg")).getBuckets().get(0).getKeyAsString());
			attrEs.setAttrValue(((ParsedStringTerms)bucket.getAggregations().get("attrs_value_agg")).getBuckets().stream().map(
				MultiBucketsAggregation.Bucket::getKeyAsString
			).collect(Collectors.toList()));
			attrEsList.add(attrEs);
		}

		long total = hits.getTotalHits().value;
		spuSearchVO.setAttrES(attrEsList);
		spuSearchVO.setSpuESList(spuESList);
		return new Page<SpuSearchVO>().setSize(query.getSize())
			.setPages((int)total%query.getSize()==0?(int)total/query.getSize():(int)total/query.getSize()+1)
			.setTotal(total)
			.setCurrent(query.getCurrent())
			.setRecords(Collections.singletonList(spuSearchVO));
	}

	/*构建查询对象*/
	private SearchRequest buildSearchRequest(SpuSearchDTO search, Query query) {
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		/*
		 *	模糊匹配 过滤
		 */
		BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
		BoolQueryBuilder keyword = QueryBuilders.boolQuery();
		//按照关键字
		if(StringUtil.isNotBlank(search.getKeyword())){
			keyword
				.should(new MatchQueryBuilder("spuTitle",search.getKeyword()))
				.should(new MatchQueryBuilder("categoryName",search.getKeyword()));
		}
		boolQueryBuilder.must(keyword);
		//按照分类
		if(StringUtil.isNotBlank(search.getCategory()))
			boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName",search.getCategory()));

		//按照库存
		boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",search.getHasStock()==1));

		//价格区间
		if(StringUtil.isNotBlank(search.getPrice())){
			String[] s = search.getPrice().split("_");
			RangeQueryBuilder priceMin = QueryBuilders.rangeQuery("priceMin");
			if(s.length==2){
				priceMin.gte(s[0]).lte(s[1]);
			}else{

				if(search.getPrice().startsWith("_")){
					priceMin.lte(s[0]);
				}else {
					priceMin.gte(s[0]);
				}
			}
			boolQueryBuilder.filter(priceMin);
		}
		//按照属性进行查询
		if(search.getAttrs()!=null&&search.getAttrs().size()>0){
			for (String attrStr : search.getAttrs()) {
				//初始化数据
				String[] s = attrStr.split("_");
				String attrId=s[0];
				String[] attrValues = s[1].split(":");
				//查询语句
				BoolQueryBuilder attrBoolQuery = QueryBuilders.boolQuery();
				attrBoolQuery.must(QueryBuilders.termQuery("attrs.id",attrId));
				attrBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
				NestedQueryBuilder attrs = QueryBuilders.nestedQuery("attrs", attrBoolQuery, ScoreMode.None);
				boolQueryBuilder.filter(attrs);
			}
		}
		/*
		* 排序 分页
		*
		*/
		if(StringUtil.isNotBlank(search.getSort())){
			String[] s = search.getSort().split("_");
			searchSourceBuilder.sort(s[0], "ASC".equals(s[1])? SortOrder.ASC:SortOrder.DESC);
		}
		searchSourceBuilder.from((query.getCurrent()-1)*query.getSize());
		searchSourceBuilder.size(query.getSize());
		/*
		*聚合分析
		*/
		//构建最外层attr_age
		NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg","attrs");
		//构建子聚合attrId
		TermsAggregationBuilder attrId_agg = AggregationBuilders.terms("attrs_id_agg").field("attrs.id").size(10);
		//构建子聚合attrId的子聚合
		attrId_agg.subAggregation(AggregationBuilders.terms("attrs_name_agg").field("attrs.attrName").size(10));
		attrId_agg.subAggregation(AggregationBuilders.terms("attrs_value_agg").field("attrs.attrValue").size(10));
		//连结
		searchSourceBuilder.aggregation(attr_agg.subAggregation(attrId_agg));
		/*
		 * 最终构建
		 */
		searchSourceBuilder.query(boolQueryBuilder);
		searchSourceBuilder.fetchSource(null,new String[]{"attrs"});

		return new SearchRequest(new String[]{"product"},searchSourceBuilder);
	}

	@SneakyThrows
	@Override
	public void soldOut(Long spuId) {
		esUtil.deleteDataById("product",spuId.toString());
	}
}
